/*
 * CGraphicEngine.cpp
 *
 *  Created on: 26.04.2009
 *      Author: markus
 */

#include "CGraphicEngine.h"
#include <exception>
#include "../Objects/CObjects.h"

CGraphicEngine::CGraphicEngine()
{
	m_OgreRoot = NULL;
        m_playerObject = NULL;
	m_RenderWindow = NULL;
}

CGraphicEngine::~CGraphicEngine()
{
	if(m_OgreRoot)
		delete m_OgreRoot;
}

/*CGraphicEngine::start()
 * In this function we start up ogre and start the render loop.
 *
 */
void CGraphicEngine::setup(std::list<BaseObject*> *objectList, ConfigContainer configContainer, Ogre::FrameListener *frameListener)
{
        if(objectList == NULL)
	{
                writeExceptionOutput("CGraphicEngine::start(): found no instance of PlayerObject. Aborting!");
	}
        m_ObjectList = objectList;

        std::list<BaseObject*>::iterator itObjectListIterator = objectList->begin();
        //itObjectListIterator++;
        m_playerObject = static_cast<LivingObject*>(*itObjectListIterator);
	Uint32 uiSDLVideoOptions;
	
	try
	{
		writeDebugOutput("Creating Root Object");	
		m_OgreRoot = new Ogre::Root(); // we create the root Object -> yes, it is that simple

		if(!m_OgreRoot)
		{
                        writeExceptionOutput("CGraphicEngine::start: could not create the root object");
		}

	// TODO: Read our own config file
// Trying to load an old config -> if that fails, we try to create a new one by using the Ogre ConfigDialog
		writeDebugOutput("Creating or restoring ogre config");
		if(!m_OgreRoot->restoreConfig())
		{
			writeDebugOutput("CGraphicEngine::start: Could not restore old config. Showing configure dialog!");

			if(!m_OgreRoot->showConfigDialog())
			{
                                writeExceptionOutput("CGraphicEngine::start: ConfigureDialog returned false -> thats bad.... aborting!");
			}
		}

// Create a SDL window
		writeDebugOutput("Creating a SDL window");
		// we need to setup a sdl window

		if(configContainer.videoConfig.bFullscreen)
		{
                        uiSDLVideoOptions = SDL_OPENGL | SDL_RESIZABLE | SDL_DOUBLEBUF | SDL_FULLSCREEN;
                        //uiSDLVideoOptions = SDL_OPENGL | SDL_RESIZABLE | SDL_FULLSCREEN;
		}
		else
		{
                    //uiSDLVideoOptions = SDL_OPENGL | SDL_RESIZABLE;
                    uiSDLVideoOptions = SDL_OPENGL | SDL_RESIZABLE | SDL_DOUBLEBUF;
                }
                SDL_SetVideoMode(configContainer.videoConfig.iResolutionX, configContainer.videoConfig.iResolutionY, configContainer.videoConfig.iBPP, uiSDLVideoOptions);


// Create a window
		writeDebugOutput("Initialising OGRE");
                m_OgreRoot->initialise(false); // For using ogre with sdl we need to do this.

// Getting SDL window informations
		writeDebugOutput("Getting SDL window informations");
		Ogre::NameValuePairList misc;
		#ifndef __gnu_linux__
			SDL_SysWMinfo wmInfo;
			SDL_VERSION(&wmInfo.version);
			SDL_GetWMInfo(&wmInfo);

			size_t winHandle = reinterpret_cast<size_t>(wmInfo.window);
			size_t winGlContext = reinterpret_cast<size_t>(wmInfo.hglrc);

			//misc["colourDepth"] = Ogre::StringConverter::toString(16);
			misc["externalWindowHandle"] = Ogre::StringConverter::toString(winHandle);
			misc["externalGLControl"] = Ogre::StringConverter::toString(true);
			misc["externalGLContext"] = Ogre::StringConverter::toString(winGlContext);
		#else
			misc["currentGLContext"] = Ogre::String("True");
		#endif

		writeDebugOutput("Creating a ogre window");
		m_RenderWindow = m_OgreRoot->createRenderWindow("Eisenreich - Alpha 0.0000001", configContainer.videoConfig.iResolutionX, configContainer.videoConfig.iResolutionY, configContainer.videoConfig.bFullscreen, &misc);
		m_RenderWindow->setVisible(true);

// Load the resources
            writeDebugOutput("LoadingResources");
            loadResources();

// Initialize resources
        writeDebugOutput("Initializing resources");
        Ogre::TextureManager::getSingleton().removeAll();
       Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(5); // TODO: find out what mipmaps are...
       Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); // TODO: if we have a lot of resources, we should divide them up (per level or something)

       createScene();

       writeDebugOutput("setting framelisteners");
       createFrameListeners(&configContainer.keyMapping);
       m_OgreRoot->addFrameListener(frameListener);

    }
    catch (Ogre::Exception &e)
    {
            writeDebugOutput(e.getFullDescription().c_str());
            if(m_OgreRoot != NULL)
            {
                delete m_OgreRoot;
                m_OgreRoot = NULL;
            }
    }
    catch(...)
    {
            writeDebugOutput("CGraphicEngine::start: Something very bad had happend, since we catched it with \"...\"!");
            if(m_OgreRoot != NULL)
            {
                delete m_OgreRoot;
                m_OgreRoot = NULL;
            }
    }

}

void CGraphicEngine::executeRenderLoop(CPhysicsMain *physicsWorld)
{
    try
    {
	   bool bContinue = true;
	   double dFPS = 60.0;
	   Ogre::Timer timerFPSCutter;

	   while(bContinue)
	   {
		   timerFPSCutter.reset();
                   if(m_OgreRoot == NULL)
                   {
                       writeExceptionOutput("No ogre root object found!");
                   }
                   bContinue = m_OgreRoot->renderOneFrame();
                   physicsWorld->advanceOneStep(timerFPSCutter.getMilliseconds()/1000.0);

		   if(timerFPSCutter.getMilliseconds() < 1000.0/dFPS)
		   {
			   SDL_Delay( 1000.0 / dFPS - timerFPSCutter.getMilliseconds() );
		   }

                SDL_GL_SwapBuffers();
           }
       //m_OgreRoot->startRendering();

	}
	catch (Ogre::Exception &e)
	{
		writeExceptionOutput(e.getFullDescription().c_str());
	}
	catch(...)
	{
		writeDebugOutput("CGraphicEngine::start: Something very bad had happend, since we catched it with \"...\"!");
	}


        if(m_OgreRoot != NULL)
        {
            delete m_OgreRoot;
            m_OgreRoot = NULL;
        }

        return;
}

/* CGraphicEngine::loadResources()
 * In this function we only get all resource definitions out of the resource file.
 * We do not need to do it this way!
 * We could easily use an xml-file, but we would have to parse it for ourselves.
 * The most important line is:
 * ResourceGroupManager::getSingleton().addResourceLocation(archName, typeName, secName);
 *
 * TODO: change the place of the resources.cfg?
 */

void CGraphicEngine::loadResources()
{
     Ogre::String secName;
     Ogre::String typeName;
     Ogre::String archName;
     Ogre::ConfigFile cf;


     cf.load("resources.cfg");

     Ogre::ConfigFile::SectionIterator secIter = cf.getSectionIterator();

     while (secIter.hasMoreElements())
     {
             Ogre::ConfigFile::SettingsMultiMap *settings;
             Ogre::ConfigFile::SettingsMultiMap::iterator i;

             secName = secIter.peekNextKey();
             settings = secIter.getNext();

             for (i = settings->begin(); i != settings->end(); ++i)
             {
                     typeName = i->first;
                     archName = i->second;
                     Ogre::ResourceGroupManager::getSingleton().addResourceLocation(archName, typeName, secName);
             }
     }
}

/*CGraphicEngine::createScene()
 * Here we create the Scene and a camera - for now...
 * Also we create a viewport without we could not render the things we see with the camera to the actual window.
 */
void CGraphicEngine::createScene()
{
	Ogre::SceneManager *scmgrTemp;
	Ogre::Camera *camTemp;

        scmgrTemp = m_OgreRoot->createSceneManager(Ogre::ST_GENERIC, "SceneManager1"); // We get a scene manager from our root object...
        camTemp = scmgrTemp->createCamera("Camera1"); // ... create a camera out of it...

        Ogre::Viewport *vp = /*m_OgreRoot->getAutoCreatedWindow()*/m_RenderWindow->addViewport(camTemp); // ... and attach this camera to a view port of the window.
//		vp->setBackgroundColour(Ogre::ColourValue(0.5,0.5,0.5));
        camTemp->setAspectRatio(Ogre::Real(vp->getActualWidth()) / Ogre::Real(vp->getActualHeight()));
        camTemp->lookAt(0,0,-1);
        camTemp->setFarClipDistance(50000);
        camTemp->setNearClipDistance(0.5);

	scmgrTemp->setAmbientLight(Ogre::ColourValue(1.0, 0.5, 0.5));//1,0.6,0.6));
        scmgrTemp->setSkyDome(true, "firststep/nachthimmel", 20, 70, 25000,false);
        //scmgrTemp->setFog(Ogre::FOG_EXP,Ogre::ColourValue::White, 0.0005,5,25000);
}

/*CGraphicEngine::createFrameListeners()
 * Here we create some (or all) frame listeners, which look for example or keyboard input, and register them to the root object.
 *
 */
bool CGraphicEngine::createFrameListeners(KeyMapping *keyMapping)
{
	// TODO: change the way the scene node for the player is created
	Ogre::SceneNode *node = m_OgreRoot->getSceneManager("SceneManager1")->getRootSceneNode()->createChildSceneNode("PlayerNode");
	Ogre::Camera *camTemp = m_OgreRoot->getSceneManager("SceneManager1")->getCamera("Camera1");
	node->attachObject(camTemp);
        m_playerObject->setSceneNode(node);
        Ogre::Vector3 camRotationVector = Ogre::Vector3(0,1,0);
        Ogre::Radian camRotationAngle(static_cast<Ogre::Real>(-90.0/180.0*Ogre::Math::PI));
        node->setOrientation(1,0,0,0);
        node->rotate(camRotationVector, camRotationAngle);
        node->translate(-20,5,0,Ogre::Node::TS_WORLD);

	return true;
}

void CGraphicEngine::prepareObject(BaseObject *object)
{
    Ogre::SceneManager *sceneManager = m_OgreRoot->getSceneManager("SceneManager1");

    switch(object->getType())
    {
    case BaseObject::TYPE_NONE:
        {
            return;
        }
    case BaseObject::TYPE_FLOOR:
        {
        Ogre::Plane planeFloor(Ogre::Vector3::UNIT_Y,0);
        long elementsX = DataConvertion::stringToType<long>(object->getProperty("elementsX"));
        long elementsY = DataConvertion::stringToType<long>(object->getProperty("elementsY"));
        Ogre::MeshManager::getSingleton().createPlane(object->getProperty("meshName"),
                                                      Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
                                                      planeFloor,
                                                      DataConvertion::stringToType<long>(object->getProperty("sizeX")),
                                                      DataConvertion::stringToType<long>(object->getProperty("sizeY")),
                                                      1,
                                                      1,
                                                      true,
                                                      1,
                                                      elementsX,
                                                      elementsY,
                                                      Ogre::Vector3::UNIT_Z
                                                      );
        Ogre::Entity *entFloor;
        entFloor = sceneManager->createEntity(object->getProperty("meshName"));
        entFloor->setMaterialName(object->getProperty("material"));
        entFloor->setCastShadows(true);
        object->movableObject(entFloor);
        Ogre::SceneNode *sceneNode = sceneManager->getRootSceneNode()->createChildSceneNode();
        object->setSceneNode(sceneNode);
        break;
        }
    case BaseObject::TYPE_SPHERE:
        {
        float scalingFactor = 1.0f/50.0f * dynamic_cast<SphereObject*>(object)->radius();
        Ogre::Entity *sphereEntitiy = sceneManager->createEntity(Ogre::SceneManager::PT_SPHERE);
        sphereEntitiy->setCastShadows(true);
        object->movableObject(sphereEntitiy);
        Ogre::SceneNode *sphereNode = sceneManager->getRootSceneNode()->createChildSceneNode();
        sphereNode->setScale(scalingFactor, scalingFactor, scalingFactor);
        object->setSceneNode(sphereNode);
        break;
        }
    case BaseObject::TYPE_LIGHT:
    {
        Ogre::Light *lightMainLight;
        lightMainLight = sceneManager->createLight();
        lightMainLight->setType(Ogre::Light::LT_POINT);
        lightMainLight->setDiffuseColour(dynamic_cast<LightObject*>(object)->getDiffuseColor());
        lightMainLight->setSpecularColour(dynamic_cast<LightObject*>(object)->getSpecularColor());
        object->movableObject(lightMainLight);
        Ogre::SceneNode *sphereNode = sceneManager->getRootSceneNode()->createChildSceneNode();
        object->setSceneNode(sphereNode);
        break;
    }
    default:
        {
            return;
        }
    }

    object->isGraphicsRelevant(true);
}

void CGraphicEngine::addObject(BaseObject *object)
{
    if(object->getSceneNode() == NULL || object->movableObject() == NULL)
    {
        writeExceptionOutput("Object not yet ready to attach. Call prepareObject() first.");
    }

    object->getSceneNode()->attachObject(object->movableObject());
}

void CGraphicEngine::removeObject(BaseObject *object)
{
    if(object->getSceneNode() == NULL || object->movableObject() == NULL)
    {
        writeExceptionOutput("no object to detach");
    }

    object->getSceneNode()->detachObject(object->movableObject());
}
